/*******************************************************************************
 * Copyright (c) 2004, 2005 Elias Volanakis and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 * Elias Volanakis - initial API and implementation
�*******************************************************************************/
package org.eclipse.gef.examples.shapes;

//import java.io.FileInputStream;
//import java.io.FileOutputStream;
//import java.io.InputStream;
//import java.io.OutputStream;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.EventObject;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.gef.ContextMenuProvider;
import org.eclipse.gef.DefaultEditDomain;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.dnd.TemplateTransferDragSourceListener;
import org.eclipse.gef.dnd.TemplateTransferDropTargetListener;
import org.eclipse.gef.editparts.ScalableFreeformRootEditPart;
import org.eclipse.gef.examples.shapes.model.Arc;
import org.eclipse.gef.examples.shapes.model.PlaceShape;
import org.eclipse.gef.examples.shapes.model.Shape;
import org.eclipse.gef.examples.shapes.model.ShapesDiagram;
import org.eclipse.gef.examples.shapes.model.TransSetShape;
import org.eclipse.gef.examples.shapes.model.TransitionShape;
import org.eclipse.gef.examples.shapes.netpetri.NetpetriUtils;
import org.eclipse.gef.examples.shapes.outlineparts.ShapesTreeEditPartFactory;
import org.eclipse.gef.examples.shapes.parts.ShapesEditPartFactory;
import org.eclipse.gef.palette.PaletteRoot;
import org.eclipse.gef.requests.CreationFactory;
import org.eclipse.gef.requests.SimpleFactory;
import org.eclipse.gef.ui.actions.ActionRegistry;
import org.eclipse.gef.ui.palette.PaletteViewer;
import org.eclipse.gef.ui.palette.PaletteViewerProvider;
import org.eclipse.gef.ui.parts.ContentOutlinePage;
import org.eclipse.gef.ui.parts.GraphicalEditorWithFlyoutPalette;
import org.eclipse.gef.ui.parts.GraphicalViewerKeyHandler;
import org.eclipse.gef.ui.parts.TreeViewer;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.util.TransferDropTargetListener;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.dialogs.SaveAsDialog;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.part.IPageSite;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;

import com.minotauro.menu.xml.ObjectFactory;
import com.minotauro.workflow.xml.netpetri.NetPetriDef;
import com.minotauro.workflow.xml.netpetri.Place;
import com.minotauro.workflow.xml.netpetri.PlaceList;
import com.minotauro.workflow.xml.netpetri.PosPlace;
import com.minotauro.workflow.xml.netpetri.PrePlace;
import com.minotauro.workflow.xml.netpetri.Trans;
import com.minotauro.workflow.xml.netpetri.TransList;
import com.minotauro.workflow.xml.netpetri.TransSet;

/**
 * A graphical editor with flyout palette that can edit .shapes files. The
 * binding between the .shapes file extension and this editor is done in
 * plugin.xml
 * 
 * @author Elias Volanakis
 */
public class ShapesEditor extends GraphicalEditorWithFlyoutPalette {

  /** This is the root of the editor's model. */
  private ShapesDiagram diagram;
  /** Palette component, holding the tools and shapes. */
  private static PaletteRoot PALETTE_MODEL;

  /** Create a new ShapesEditor instance. This is called by the Workspace. */
  public ShapesEditor() {
    setEditDomain(new DefaultEditDomain(this));
  }

  //  public IPropertyDescriptor[] getPropertyDescriptors() {
  //    return diagram.getPropertyDescriptors();
  //  }
  //
  //  public Object getPropertyValue(Object propertyId) {
  //    return diagram.getPropertyValue(propertyId);
  //  }
  //
  //  public void setPropertyValue(Object propertyId, Object value) {
  //    diagram.setPropertyValue(propertyId, value);
  //  }

  /**
   * Configure the graphical viewer before it receives contents.
   * <p>
   * This is the place to choose an appropriate RootEditPart and
   * EditPartFactory for your editor. The RootEditPart determines the behavior
   * of the editor's "work-area". For example, GEF includes zoomable and
   * scrollable root edit parts. The EditPartFactory maps model elements to
   * edit parts (controllers).
   * </p>
   * 
   * @see org.eclipse.gef.ui.parts.GraphicalEditor#configureGraphicalViewer()
   */
  protected void configureGraphicalViewer() {
    System.err.println("ShapesEditor configureGraphicalViewer");
    super.configureGraphicalViewer();

    GraphicalViewer viewer = getGraphicalViewer();
    viewer.setEditPartFactory(new ShapesEditPartFactory());
    viewer.setRootEditPart(new ScalableFreeformRootEditPart());
    viewer.setKeyHandler(new GraphicalViewerKeyHandler(viewer));

    // configure the context menu provider
    ContextMenuProvider cmProvider = new ShapesEditorContextMenuProvider(viewer, getActionRegistry());
    viewer.setContextMenu(cmProvider);
    getSite().registerContextMenu(cmProvider, viewer);
  }

  protected void createActions() {
    super.createActions(); // important else we won't get the default
    // actions!
    //		IAction action = new RotateAction(this);
    //		getActionRegistry().registerAction(action);
    //		getSelectionActions().add(action.getId());
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.eclipse.gef.ui.parts.GraphicalEditor#commandStackChanged(java.util
   * .EventObject)
   */
  public void commandStackChanged(EventObject event) {
    System.err.println("ShapesEditor commandStackChanged");
    firePropertyChange(IEditorPart.PROP_DIRTY);
    super.commandStackChanged(event);
  }

  private void createOutputStream(OutputStream os) throws IOException {
    System.err.println("ShapesEditor createOutputStream");
    ObjectOutputStream oos = new ObjectOutputStream(os);
    oos.writeObject(getModel());
    oos.close();
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.eclipse.gef.ui.parts.GraphicalEditorWithFlyoutPalette#
   * createPaletteViewerProvider()
   */
  protected PaletteViewerProvider createPaletteViewerProvider() {
    System.err.println("ShapesEditor createPaletteViewerProvider");
    return new PaletteViewerProvider(getEditDomain()) {
      protected void configurePaletteViewer(PaletteViewer viewer) {
        super.configurePaletteViewer(viewer);
        // create a drag source listener for this palette viewer
        // together with an appropriate transfer drop target listener,
        // this will enable
        // model element creation by dragging a
        // CombinatedTemplateCreationEntries
        // from the palette into the editor
        // @see ShapesEditor#createTransferDropTargetListener()
        viewer.addDragSourceListener(new TemplateTransferDragSourceListener(viewer));
      }
    };
  }

  /**
   * Create a transfer drop target listener. When using a
   * CombinedTemplateCreationEntry tool in the palette, this will enable model
   * element creation by dragging from the palette.
   * 
   * @see #createPaletteViewerProvider()
   */
  private TransferDropTargetListener createTransferDropTargetListener() {
    System.err.println("ShapesEditor createTransferDropTargetListener");
    return new TemplateTransferDropTargetListener(getGraphicalViewer()) {
      protected CreationFactory getFactory(Object template) {
        return new SimpleFactory((Class) template);
      }
    };
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.eclipse.ui.ISaveablePart#doSave(org.eclipse.core.runtime.IProgressMonitor
   * )
   */
  public void doSave(IProgressMonitor monitor) {
    System.err.println("ShapesEditor doSave " + getEditorInput() + " " + getEditorInput().getName());
    try {

      FileOutputStream fos = new FileOutputStream(getEditorInput().getName());
      NetPetriDef xmlNetPetriDef = toNetPetriDef();
      try {
        JAXBContext jc = JAXBContext.newInstance(ObjectFactory.class.getPackage().getName());
        Marshaller marshaller = jc.createMarshaller();
        marshaller.marshal(xmlNetPetriDef, fos);
        try {
          fos.close();
        } catch (IOException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
        }
      } catch (JAXBException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
      }

    } catch (FileNotFoundException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

    //    ByteArrayOutputStream out = new ByteArrayOutputStream();
    //    try {
    //      createOutputStream(out);
    //      IFile file = ((IFileEditorInput) getEditorInput()).getFile();
    //      file.setContents(new ByteArrayInputStream(out.toByteArray()), true, // keep
    //          // saving,
    //          // even
    //          // if
    //          // IFile
    //          // is
    //          // out
    //          // of
    //          // sync
    //          // with
    //          // the
    //          // Workspace
    //          false, // dont keep history
    //          monitor); // progress monitor
    //      getCommandStack().markSaveLocation();
    //    } catch (CoreException ce) {
    //      ce.printStackTrace();
    //    } catch (IOException ioe) {
    //      ioe.printStackTrace();
    //    }
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.eclipse.ui.ISaveablePart#doSaveAs()
   */
  public void doSaveAs() {
    System.err.println("ShapesEditor doSaveAs");
    // Show a SaveAs dialog
    Shell shell = getSite().getWorkbenchWindow().getShell();
    SaveAsDialog dialog = new SaveAsDialog(shell);
    dialog.setOriginalFile(((IFileEditorInput) getEditorInput()).getFile());
    dialog.open();

    IPath path = dialog.getResult();
    if (path != null) {
      // try to save the editor's contents under a different file name
      final IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
      try {
        new ProgressMonitorDialog(shell).run(false, // don't fork
            false, // not cancelable
            new WorkspaceModifyOperation() { // run this operation
              public void execute(final IProgressMonitor monitor) {
                try {
                  ByteArrayOutputStream out = new ByteArrayOutputStream();
                  createOutputStream(out);
                  file.create(new ByteArrayInputStream(out.toByteArray()), // contents
                      true, // keep saving, even if IFile
                      // is out of sync with the
                      // Workspace
                      monitor); // progress monitor
                } catch (CoreException ce) {
                  ce.printStackTrace();
                } catch (IOException ioe) {
                  ioe.printStackTrace();
                }
              }
            });
        // set input to the new file
        setInput(new FileEditorInput(file));
        getCommandStack().markSaveLocation();
      } catch (InterruptedException ie) {
        // should not happen, since the monitor dialog is not cancelable
        ie.printStackTrace();
      } catch (InvocationTargetException ite) {
        ite.printStackTrace();
      }
    }
  }

  public NetPetriDef toNetPetriDef() {
    NetpetriUtils utils = new NetpetriUtils();

    NetPetriDef npd = new NetPetriDef();
    npd.setDefaultDocSectionEditState(diagram.getDefaultDocSectionEditState());
    npd.setDefaultDocSectionValidateState(diagram.getDefaultDocSectionValidateState());
    npd.setDefaultDocSectionViewState(diagram.getDefaultDocSectionViewState());
    npd.setDocType(diagram.getDocType());
    npd.setName(diagram.getName());
    npd.getMetaData().addAll(diagram.getMetaData());
    npd.setStateList(diagram.getStateList());
    npd.setRolesList(diagram.getRolesList());

    PlaceList placeList = new PlaceList();
    TransList transList = new TransList();

    //for all shapes
    for (Shape shape : diagram.getChildren()) {
      //if is a PlaceShape
      if (shape instanceof PlaceShape) {
        //add to the placeList
        PlaceShape p = (PlaceShape) shape;
        placeList.getPlace().add(utils.createPlace(p.getMetaData(), p.getName(), p.getTokens()));
      } else if (shape instanceof TransSetShape) { //if is TransSetShape
        TransSetShape ts = (TransSetShape) shape;
        List<Trans> trans = new ArrayList<Trans>();
        //for any Child of this TransSetShape
        for (Shape shape2 : ts.getChildren()) {
          //Create Trans
          TransitionShape tr = (TransitionShape) shape2;
          Trans transition = utils.createTrans(tr.getMetaData(), tr.getDefaultDocSectionState(),
              tr.getDocSectionState(), tr.getName());

          //For all SourceConnection
          for (Arc arc : tr.getSourceConnections()) {
            //add PosPlace to the Trans
            PlaceShape tg = (PlaceShape) arc.getTarget();
            transition.getPosPlace().add(utils.createPosPlace(tg.getName()));
          }

          //for all TargetConnection
          for (Arc arc : tr.getTargetConnections()) {
            //add PrePlace
            PlaceShape sr = (PlaceShape) arc.getSource();
            transition.getPrePlace().add(utils.createPrePlace(sr.getName(), arc.isInhibitor()));
          }
          //add trans
          trans.add(transition);

        }
        //add TransSet
        transList.getTransSet().add(
            utils.createTransSet(utils.createAuto(ts.getTime(), ts.getType()),
                utils.createRole(ts.getDelegator(), ts.getRoleName()), utils.createWork(ts.getWork()),
                ts.getDefaultDocSectionState(), ts.getDocSectionState(), ts.getMetaData(), trans, ts.getInit(),
                ts.getTSName()));
      }
    }
    // set PlaceList & TransList
    npd.setPlaceList(placeList);
    npd.setTransList(transList);

    return npd;
  }

  public void toShapesDiagram(NetPetriDef netPetriDef) {
    List<Shape> shapes = new ArrayList<Shape>();

    for (Place place : netPetriDef.getPlaceList().getPlace()) {
      PlaceShape p = new PlaceShape(place.getMetaData(), place.getTokens(), place.getName());
      p.setLocation(new Point(Integer.parseInt(p.findMetaData("Shape.xPos").getVal()), Integer.parseInt(p.findMetaData(
          "Shape.yPos").getVal())));
      p.setSize(new Dimension(Integer.parseInt(p.findMetaData("Shape.Width").getVal()), Integer.parseInt(p
          .findMetaData("Shape.Height").getVal())));
      shapes.add(p);
    }

    for (TransSet transSet : netPetriDef.getTransList().getTransSet()) {
      List<Shape> transition = new ArrayList<Shape>();

      for (Trans trans : transSet.getTrans()) {
        TransitionShape tr = new TransitionShape(trans.getMetaData(), trans.getDefaultDocSectionState(),//
            trans.getDocSectionState(), trans.getName());
        tr.setLocation(new Point(Integer.parseInt(tr.findMetaData("Shape.xPos").getVal()),//
            Integer.parseInt(tr.findMetaData("Shape.yPos").getVal())));
        tr.setSize(new Dimension(Integer.parseInt(tr.findMetaData("Shape.Width").getVal()),//
            Integer.parseInt(tr.findMetaData("Shape.Height").getVal())));
        transition.add(tr);
      }

      int roleType;

      if (transSet.getAuto().getType() == "") {
        roleType = 0;
      } else {
        roleType = 1;
      }
      TransSetShape ts = new TransSetShape(transSet.getDefaultDocSectionState(), transSet.getDocSectionState(),//
          transSet.getMetaData(), transition, transSet.getName(), roleType, transSet.getRole().getName(),//
          transSet.getWork().getName(), transSet.getRole().getDelegator(), transSet.getAuto().getTime(),//
          transSet.getAuto().getType(), transSet.isInit());

      ts.setLocation(new Point(Integer.parseInt(ts.findMetaData("Shape.xPos").getVal()),//
          Integer.parseInt(ts.findMetaData("Shape.yPos").getVal())));
      ts.setSize(new Dimension(Integer.parseInt(ts.findMetaData("Shape.Width").getVal()),//
          Integer.parseInt(ts.findMetaData("Shape.Height").getVal())));

      shapes.add(ts);
    }

    diagram = new ShapesDiagram(shapes, netPetriDef.getMetaData(), netPetriDef.getDefaultDocSectionValidateState(),
        netPetriDef.getDefaultDocSectionEditState(), //
        netPetriDef.getDefaultDocSectionViewState(), netPetriDef.getRolesList(), netPetriDef.getDocType(),
        netPetriDef.getName());

    // for all trans-set
    for (TransSet transSet : netPetriDef.getTransList().getTransSet()) {

      // for all trans
      for (Trans trans : transSet.getTrans()) {

        // for all pre places
        for (PrePlace pre : trans.getPrePlace()) {
          PlaceShape src = (PlaceShape) diagram.getPlaceChild(pre.getName());

          TransitionShape tgt = (TransitionShape) diagram.getTransitionChild(trans.getName());

          // TODO: validate

          Arc arc = new Arc(true, pre.isInhibitor() ? Graphics.LINE_DASH : Graphics.LINE_SOLID,//
              src, tgt);

          src.getSourceConnections().add(arc);
          tgt.getTargetConnections().add(arc);
        }

        //for all pos places
        for (PosPlace pos : trans.getPosPlace()) {
          PlaceShape tgt = (PlaceShape) diagram.getPlaceChild(pos.getName());
          TransitionShape src = (TransitionShape) diagram.getTransitionChild(trans.getName());
          Arc e = new Arc(true, Graphics.LINE_SOLID, src, tgt);
          src.getSourceConnections().add(e);
          tgt.getTargetConnections().add(e);
        }
      }
    }

  }

  public Object getAdapter(Class type) {
    System.err.println("ShapesEditor getAdapter");
    if (type == IContentOutlinePage.class)
      return new ShapesOutlinePage(new TreeViewer());
    return super.getAdapter(type);
  }

  ShapesDiagram getModel() {
    return diagram;
  }

  /*
   * (non-Javadoc)
   * 
   * @see
   * org.eclipse.gef.ui.parts.GraphicalEditorWithFlyoutPalette#getPaletteRoot
   * ()
   */
  protected PaletteRoot getPaletteRoot() {
    System.err.println("ShapesEditor getPaletteRoot");
    if (PALETTE_MODEL == null)
      PALETTE_MODEL = ShapesEditorPaletteFactory.createPalette();
    return PALETTE_MODEL;
  }

  private void handleLoadException(Exception e) {
    System.err.println("ShapesEditor handleLoadException");
    System.err.println("** Load failed. Using default model. **");
    e.printStackTrace();
    diagram = new ShapesDiagram();
  }

  /**
   * Set up the editor's inital content (after creation).
   * 
   * @see org.eclipse.gef.ui.parts.GraphicalEditorWithFlyoutPalette#initializeGraphicalViewer()
   */
  protected void initializeGraphicalViewer() {
    System.err.println("ShapesEditor initializeGraphicalViewer");
    super.initializeGraphicalViewer();
    GraphicalViewer viewer = getGraphicalViewer();
    viewer.setContents(getModel()); // set the contents of this editor

    // listen for dropped parts
    viewer.addDropTargetListener(createTransferDropTargetListener());
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.eclipse.ui.ISaveablePart#isSaveAsAllowed()
   */
  public boolean isSaveAsAllowed() {
    return true;
  }

  /*
   * (non-Javadoc)
   * 
   * @see org.eclipse.ui.part.EditorPart#setInput(org.eclipse.ui.IEditorInput)
   */
  protected void setInput(IEditorInput input) {
    System.err.println("ShapesEditor setInput");
    super.setInput(input);
    try {
      FileInputStream fis = new FileInputStream(getEditorInput().getName());
      JAXBContext jc;
      jc = JAXBContext.newInstance( //
          ObjectFactory.class.getPackage().getName());
      Unmarshaller unmarshaller = jc.createUnmarshaller();

      NetPetriDef xmlNetPetriDef = (NetPetriDef) unmarshaller.unmarshal(fis);
      toShapesDiagram(xmlNetPetriDef);
      fis.close();
      //      setPartName(getEditorInput().getName());
    } catch (JAXBException e) {
      // TODO Auto-generated catch block
      handleLoadException(e);
    } catch (FileNotFoundException e) {
      // TODO Auto-generated catch block
      handleLoadException(e);
    } catch (IOException e) {
      // TODO Auto-generated catch block
      handleLoadException(e);
    };
  }

  /**
   * Creates an outline pagebook for this editor.
   */
  public class ShapesOutlinePage extends ContentOutlinePage {
    /**
     * Create a new outline page for the shapes editor.
     * 
     * @param viewer
     *            a viewer (TreeViewer instance) used for this outline page
     * @throws IllegalArgumentException
     *             if editor is null
     */
    public ShapesOutlinePage(EditPartViewer viewer) {
      super(viewer);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.eclipse.ui.part.IPage#createControl(org.eclipse.swt.widgets.Composite
     * )
     */
    public void createControl(Composite parent) {
      // create outline viewer page
      getViewer().createControl(parent);
      // configure outline viewer
      getViewer().setEditDomain(getEditDomain());
      getViewer().setEditPartFactory(new ShapesTreeEditPartFactory());
      // configure & add context menu to viewer
      ContextMenuProvider cmProvider = new ShapesEditorContextMenuProvider(getViewer(), getActionRegistry());
      getViewer().setContextMenu(cmProvider);
      getSite().registerContextMenu("org.eclipse.gef.examples.shapes.outline.contextmenu", cmProvider,
          getSite().getSelectionProvider());
      // hook outline viewer
      getSelectionSynchronizer().addViewer(getViewer());
      // initialize outline viewer with model
      getViewer().setContents(getModel());
      // show outline viewer
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.ui.part.IPage#dispose()
     */
    public void dispose() {
      // unhook outline viewer
      getSelectionSynchronizer().removeViewer(getViewer());
      // dispose
      super.dispose();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.ui.part.IPage#getControl()
     */
    public Control getControl() {
      return getViewer().getControl();
    }

    /**
     * @see org.eclipse.ui.part.IPageBookViewPage#init(org.eclipse.ui.part.IPageSite)
     */
    public void init(IPageSite pageSite) {
      super.init(pageSite);
      ActionRegistry registry = getActionRegistry();
      IActionBars bars = pageSite.getActionBars();
      String id = ActionFactory.UNDO.getId();
      bars.setGlobalActionHandler(id, registry.getAction(id));
      id = ActionFactory.REDO.getId();
      bars.setGlobalActionHandler(id, registry.getAction(id));
      id = ActionFactory.DELETE.getId();
      bars.setGlobalActionHandler(id, registry.getAction(id));
    }
  }

}